package com.github.itdachen.gateway.filter.oplog;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.github.itdachen.framework.cloud.jwt.parse.verified.IVerifiedTicketUrlService;
import com.github.itdachen.framework.cloud.jwt.IVerifyTicketTokenHelper;
import com.github.itdachen.framework.context.BizContextHandler;
import com.github.itdachen.framework.context.constants.UserInfoConstant;
import com.github.itdachen.framework.context.handler.GlobalContextUserDetailsHandler;
import com.github.itdachen.framework.context.jwt.IJwtInfo;
import com.github.itdachen.framework.context.permission.CheckPermissionInfo;
import com.github.itdachen.framework.context.userdetails.UserInfoDetails;
import com.github.itdachen.framework.core.response.ServerResponse;
import com.github.itdachen.framework.core.utils.StringUtils;
import com.github.itdachen.gateway.ignore.IIgnoreMatchersService;
import com.github.itdachen.gateway.oplog.IGatewayOplogHandler;
import com.github.itdachen.gateway.permissions.ICheckPermissionInfo;
import jakarta.validation.constraints.NotNull;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.http.server.logging.AccessLog;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;

/**
 * OplogGlobalFilter Access
 *
 * @author 王大宸
 * @date 2025-02-07 9:31
 */
@Component
public class AccessOplogGlobalFilter implements GlobalFilter, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(AccessOplogGlobalFilter.class);


    private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();


    private final IVerifyTicketTokenHelper verifyTicketTokenService;
    private final IVerifiedTicketUrlService verifiedTicketUrlService;
    private final ICheckPermissionInfo checkPermissionInfo;
    private final IGatewayOplogHandler gatewayOplogHandler;
    private final IIgnoreMatchersService ignoreMatchersService;

    public AccessOplogGlobalFilter(IVerifyTicketTokenHelper verifyTicketTokenService,
                                   IVerifiedTicketUrlService verifiedTicketUrlService,
                                   ICheckPermissionInfo checkPermissionInfo,
                                   IGatewayOplogHandler gatewayOplogHandler,
                                   IIgnoreMatchersService ignoreMatchersService) {
        this.verifyTicketTokenService = verifyTicketTokenService;
        this.verifiedTicketUrlService = verifiedTicketUrlService;
        this.checkPermissionInfo = checkPermissionInfo;
        this.gatewayOplogHandler = gatewayOplogHandler;
        this.ignoreMatchersService = ignoreMatchersService;
    }


    /**
     * 顺序必须是 <-1，否则标准的 NettyWriteResponseFilter 将在您的过滤器得到一个被调用的机会之前发送响应
     * 也就是说如果不小于 -1 ，将不会执行获取后端响应的逻辑
     *
     * @return
     */
    @Override
    public int getOrder() {
        return -50;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange webExchange, GatewayFilterChain filterChain) {
        logger.info("check token and user permission....");
        // LinkedHashSet requiredAttribute = webExchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        // 将 Request 中可以直接获取到的参数，设置到网关日志
        ServerHttpRequest request = webExchange.getRequest();

        // 获取当前网关访问的URI
        String requestUri = request.getPath().pathWithinApplication().value();
//        if (requiredAttribute != null) {
//            Iterator<URI> iterator = requiredAttribute.iterator();
//            while (iterator.hasNext()) {
//                URI next = iterator.next();
//                if (next.getPath().startsWith(GATE_WAY_PREFIX)) {
//                    requestUri = next.getPath().substring(GATE_WAY_PREFIX.length());
//                }
//            }
//        }

        /* 判断是否开放路径 */
        BizContextHandler.setToken(null);
        ServerHttpRequest.Builder mutate = request.mutate();

        // 网关不进行拦截的URI配置，常见如验证码、Login接口
        boolean ignoreMatchers = ignoreMatchersService.ignoreMatchers(requestUri);
        if (ignoreMatchers) {
            return filterChain.filter(webExchange);
        }


        /* 获取当前用户信息 */
        UserInfoDetails userInfoDetails = null;
        try {
            // 判断用户token，获取用户信息
            userInfoDetails = getUserDetails(request, mutate);
        } catch (Exception e) {
            logger.error("用户 Token 过期异常 [User Token Error or Expired]", e);
            return getVoidMono(webExchange, ServerResponse.err(HttpStatus.UNAUTHORIZED.value(), "User Token Error or Expired!", "User Token Error or Expired!"), HttpStatus.UNAUTHORIZED);
        }

        /* 记录操作日志, 日志基础信息入库 */

        // gatewayOplogHandler.saveOplog(webExchange, userInfoDetails, oplogId);

        /* 权限校验 */
        Mono<CheckPermissionInfo> checkPermissionInfoMono = checkPermissionInfo.checkPermissionInfoMono(userInfoDetails, request.getMethod().name(), requestUri);


        final UserInfoDetails userDetails = userInfoDetails;
        return checkPermissionInfoMono.flatMap(checkPermissionInfo -> {

            /* 不需要鉴权的资源 */
            if (null == checkPermissionInfo.getPermissionInfo()) {
                return filterChain.filter(webExchange);
            }


            final String oplogId = gatewayOplogHandler.saveOplog(webExchange, userDetails, checkPermissionInfo.getPermissionInfo());
            // 当前用户具有访问权限
            if (!checkPermissionInfo.getIsAuth()) {
                // 当前用户不具有访问权限,更新权限不足信息
                ServerResponse<Object> err = ServerResponse.err(403, "权限不足！", "Forbidden！Does not has Permission！");
                gatewayOplogHandler.deniedPermission(userDetails,
                        checkPermissionInfo.getPermissionInfo(),
                        err,
                        oplogId);
                return getVoidMono(webExchange, err, HttpStatus.FORBIDDEN);
            }


            // 追加日志基础信息
//            gatewayOplogHandler.appendFuncOplog(userDetails, checkPermissionInfo.getPermissionInfo(), oplogId);
//            ServerHttpRequest build = mutate.build();
//            return filterChain.filter(webExchange.mutate().request(build).build());


            // 继续 filter 过滤
            MediaType mediaType = request.getHeaders().getContentType();
            if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)
                    || MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) { // 适合 JSON 和 Form 提交的请求
                return filterWithRequestBody(webExchange, filterChain, userDetails, oplogId);
            }
            return filterWithoutRequestBody(webExchange, filterChain, userDetails, oplogId);

        });


    }

    /******* 隔离带 *******************************************************************************************************************************************************************************/


    /***
     * 网关抛异常
     *
     * @author 王大宸
     * @date 2025/2/10 9:42
     * @param serverWebExchange serverWebExchange
     * @param body body
     * @param status status
     * @return reactor.core.publisher.Mono<java.lang.Void>
     */
    @NotNull
    private Mono<Void> getVoidMono(ServerWebExchange serverWebExchange, ServerResponse<Object> body, HttpStatus status) {
        serverWebExchange.getResponse().setStatusCode(status);
        byte[] bytes = JSONObject.toJSONString(body).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(bytes);
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }


    /**
     * 没有请求体的请求只需要记录日志
     *
     * @param exchange
     * @param chain
     * @param userInfoDetails 用户信息
     * @param oplogId         日志ID
     * @return
     */
    private Mono<Void> filterWithoutRequestBody(ServerWebExchange exchange, GatewayFilterChain chain,
                                                final UserInfoDetails userInfoDetails,
                                                final String oplogId) {

        final String queryParams = getQueryParams(exchange.getRequest());
        gatewayOplogHandler.appendRequestBody(userInfoDetails, queryParams, oplogId);

        // 包装 Response，用于记录 Response Body
        ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, userInfoDetails, oplogId);

        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    /**
     * 需要读取请求体
     * 参考 {@link ModifyRequestBodyGatewayFilterFactory} 实现
     */
    private Mono<Void> filterWithRequestBody(ServerWebExchange exchange, GatewayFilterChain chain,
                                             final UserInfoDetails userInfoDetails,
                                             final String oplogId) {
        // 设置 Request Body 读取时，设置到网关日志
        ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);

        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            //   gatewayLog.setRequestBody(body);

            logger.info("filter With Request Body: {}", body);
            gatewayOplogHandler.appendRequestBody(userInfoDetails, body, oplogId);
            return Mono.just(body);
        });

        // 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次
        BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        // the new content type will be computed by bodyInserter
        // and then set in the request decorator
        headers.remove(HttpHeaders.CONTENT_LENGTH);

        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);

        // 通过 BodyInserter 将 Request Body 写入到 CachedBodyOutputMessage 中
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            // 重新封装请求
            ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage);
            // 记录响应日志
            ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, userInfoDetails, oplogId);

            // 记录普通的
            return chain.filter(exchange.mutate()
                    .request(decoratedRequest)
                    .response(decoratedResponse)
                    .build());

//            return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build())
//                    .then(Mono.fromRunnable(() -> writeAccessLog(gatewayLog))); // 打印日志

        }));
    }


    /***
     * 获取请求路径上的查询参数
     *
     * @author 王大宸
     * @date 2025/2/17 10:46
     * @param request request
     * @return java.lang.String
     */
    private String getQueryParams(ServerHttpRequest request) {
        MultiValueMap<String, String> params = request.getQueryParams();
        StringBuffer sb = new StringBuffer("{");
        int index = 1;
        for (String key : params.keySet()) {
            for (String value : params.get(key)) {
                sb.append("\"").append(key).append("\"")
                        .append(":\"")
                        .append(value).append("\"");
            }
            if (index != params.size()) {
                sb.append(",");
            }
            index++;
        }
        sb.append("}");
        return sb.toString();
    }


    /**
     * 返回 token 中的用户信息
     *
     * @param request
     * @param ctx
     * @return
     */
    private UserInfoDetails getUserDetails(ServerHttpRequest request, ServerHttpRequest.Builder ctx) throws Exception {

        List<String> strings = request.getHeaders().get(UserInfoConstant.HEADER_AUTHORIZATION);
        String authToken = null;
        if (strings != null) {
            authToken = strings.get(0);
        }
        if (StringUtils.isBlank(authToken)) {
            strings = request.getQueryParams().get("token");
            if (strings != null) {
                authToken = strings.get(0);
            }
        }

        String token = authToken.replaceAll("%20", " ");
        if (token.startsWith(UserInfoConstant.TOKEN_TYPE)) {
            token = token.substring(UserInfoConstant.TOKEN_TYPE.length());
        }

        final IJwtInfo ijwtInfo = verifyTicketTokenService.parseToken(token);

        /* 是否将 token 存放在 redis 中校验是否过期 */
//        String s = stringRedisTemplate.opsForValue().get(RedisKeyConstant.REDIS_KEY_TOKEN + ":" + ijwtInfo.getTokenId());
//        if (StringUtils.isBlank(s)) {
//            throw new UserTokenException("User token expired!");
//        }


        ctx.header(UserInfoConstant.HEADER_AUTHORIZATION, authToken);
        BizContextHandler.setToken(authToken);
        return GlobalContextUserDetailsHandler.setUserDetails(ijwtInfo);


    }

    /**
     * 记录响应日志
     * 通过 DataBufferFactory 解决响应体分段传输问题。
     */
    private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, UserInfoDetails userInfoDetails, String oplogId) {
        ServerHttpResponse response = exchange.getResponse();

        return new ServerHttpResponseDecorator(response) {

            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    DataBufferFactory bufferFactory = response.bufferFactory();

                    // 获取响应类型，如果是 json 就打印
                    String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);

                    if (StrUtil.isNotBlank(originalResponseContentType) && originalResponseContentType.contains("application/json")) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);

                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            // 设置 response body 到网关日志
                            byte[] content = readContent(dataBuffers);
                            String responseResult = new String(content, StandardCharsets.UTF_8);

                            //  logger.info("recordResponseLog: {}", responseResult);

                            /* 响应数据入库 */
                            gatewayOplogHandler.appendResponseBody(userInfoDetails, responseResult, oplogId);

                            // 响应
                            return bufferFactory.wrap(content);
                        }));
                    }
                }
                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };
    }


    /**
     * 请求装饰器，支持重新计算 headers、body 缓存
     *
     * @param exchange      请求
     * @param headers       请求头
     * @param outputMessage body 缓存
     * @return 请求装饰器
     */
    private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {

            @Override
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    // TODO: this causes a 'HTTP/1.1 411 Length Required' // on
                    // httpbin.org
                    httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }

    /**
     * 从dataBuffers中读取数据
     *
     * @author jam
     * @date 2024/5/26 22:31
     */
    private byte[] readContent(List<? extends DataBuffer> dataBuffers) {
        // 合并多个流集合，解决返回体分段传输
        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
        DataBuffer join = dataBufferFactory.join(dataBuffers);
        byte[] content = new byte[join.readableByteCount()];
        join.read(content);
        // 释放掉内存
        DataBufferUtils.release(join);
        return content;
    }


    /***
     * URI是否以什么打头
     *
     * @author 王大宸
     * @date 2025/2/10 9:46
     * @param requestUri requestUri
     * @return boolean
     */
    private boolean isStartWith(String requestUri) {
        if (requestUri.startsWith("/api/auth/authorized")) {
            return true;
        }
        if (requestUri.startsWith("/api/auth/oauth/jwt")) {
            return true;
        }
        return false;
    }


}
